/**
 * Copyright (c) 2009 blurredVision
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
 * associated documentation files (the "Software"), to deal in the Software without restriction, 
 * including without limitation the rights to use, copy, modify, merge, publish, distribute, 
 * sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is 
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or 
 * substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT 
 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT 
 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "StdAfx.h"
#include "CubeMap.h"
#include "Texture.h"
#include "IRenderer.h"

namespace directx {

CubeMap::CubeMap(void)
:	pCubeTexture(NULL),
	pDepthCube(NULL)
{
}

CubeMap::~CubeMap(void)
{
	release();
}

void CubeMap::release()
{
	RELEASE( pCubeTexture );
	RELEASE( pDepthCube );
}

void CubeMap::reset()
{
	DXResource::reset();

	if (m_bIsLoaded) 
	{
		if (isRenderTarget())
			createRenderTarget(m_iWidth, m_iHeight, m_eFormat);
		else 
			load(sFilename);
	}
}


bool CubeMap::load(const std::string &filename, bool dynamic, eFormat format)
{
	sFilename = filename;
	D3DXIMAGE_INFO info;
	D3DFORMAT targetformat;

	if ( FAILED( D3DXGetImageInfoFromFile( filename.c_str(), &info) )) {
		coreGetLog()->WriteWarning("Could not get image info from file " + filename, __FILE__, __LINE__);
	}
	else 
	{
		m_iWidth = info.Width;
		m_iHeight = info.Height;
		m_iDepth = info.Depth;
		switch (info.Format) {
			case D3DFMT_A16B16G16R16F:
				m_eFormat = A16B16G16R16F;
				break;
			case D3DFMT_A32B32G32R32F:
				m_eFormat = A32B32G32R32F;
				break;
		};
		targetformat = info.Format;
		if (format != DEFAULT) {
			targetformat = Texture::convertFormatToD3D(format);
		}
	}
	// FIX FOR ATI: Fucking vendor doesn't support FP16 filtering. So we choose RGB8. This should be fixed later on!!!!
	// I hate those ATI guys.
	if (coreGetRenderer()->supportsFP16Filter())
		targetformat = D3DFMT_A8B8G8R8;

	DWORD usage = 0;
	if (dynamic) 
		usage = D3DUSAGE_DYNAMIC;

	if ( FAILED( D3DXCreateCubeTextureFromFileEx( pd3dDevice,			// Device
													filename.c_str(),	// Source file
													D3DX_DEFAULT,		// Size
													D3DX_DEFAULT,		// MipLevels
													D3DUSAGE_DYNAMIC,	// Usage
													targetformat,		// Format
													D3DPOOL_DEFAULT,	// Pool
													D3DX_DEFAULT,		// Filter
													D3DX_DEFAULT,		// MipFilter
													0xFF000000,			// ColorKey
													&info,				// Source Info
													NULL,				// Palette
													&pCubeTexture) ))	// Target
	{
		coreGetLog()->WriteWarning("Could not load cube texture " + filename, __FILE__, __LINE__);
		return false;
	}

	//if (!dynamic) 
	{
		// Create mipmap levels for the 2D Texture
		if ( FAILED( D3DXFilterCubeTexture( pCubeTexture, NULL, D3DX_DEFAULT, D3DX_DEFAULT) ))
		{
			coreGetLog()->WriteError("Could not create direct3d mipmap levels for Cube texture.", __FILE__, __LINE__);
		}
	}

	m_bIsCubeMap = true;
	m_bIsLoaded = true;

	return true;
}

bool CubeMap::bind(unsigned int uiTexTarget)
{
	coreGetLog()->WriteWarning("Could not bind cube texture. Not implemented. " , __FILE__, __LINE__);
	return false;
}

bool CubeMap::unbind()
{
	coreGetLog()->WriteWarning("Could not unbind cube texture. Not implemented. ", __FILE__, __LINE__);
	return false;
}

bool CubeMap::createRenderTarget(int width, int height, eFormat format, bool useDepth)
{
	D3DFORMAT targetformat;
	m_eFormat = format;
	if (format != DEFAULT) {
		targetformat = Texture::convertFormatToD3D(format);
	}

	if (FAILED( D3DXCreateCubeTexture(	pd3dDevice, 
										width,
										3, 
										D3DUSAGE_RENDERTARGET,
										targetformat, 
										D3DPOOL_DEFAULT, 
										&pCubeTexture ))) {
	}

	if (FAILED( pd3dDevice->CreateDepthStencilSurface(	width, 
														width, 
														D3DFMT_D24S8, 
														D3DMULTISAMPLE_NONE, 
														0, 
														TRUE, 
														&pDepthCube, 
														NULL))) {
	}


	coreGetLog()->WriteWarning("Could not create cube texture render target. Not implemented. ", __FILE__, __LINE__);
	return false;
}

bool CubeMap::createRenderToTexture(int size, eFormat format, bool usedepth)
{
	D3DFORMAT targetformat;
	m_eFormat = format;
	if (format != DEFAULT) {
		targetformat = Texture::convertFormatToD3D(format);
	}

	if (FAILED( D3DXCreateCubeTexture(	pd3dDevice, 
										size,
										0, 
										D3DUSAGE_RENDERTARGET,
										targetformat, 
										D3DPOOL_DEFAULT, 
										&pCubeTexture ))) 
	{
		coreGetLog()->WriteError("Could not create cube texture", __FILE__, __LINE__);
		return false;
	}

	if (FAILED( D3DXCreateRenderToEnvMap(pd3dDevice, size, 0, targetformat, usedepth, D3DFMT_D24S8, &m_pRTEnv) ))
	{
		coreGetLog()->WriteError("Could not create cube texture", __FILE__, __LINE__);
		return false;
	}

	for (int i=0; i<6; ++i) {
		if (FAILED( pCubeTexture->GetCubeMapSurface((D3DCUBEMAP_FACES)i, 0, &m_pSurfaces[i]))) {
			coreGetLog()->WriteError("Could not get handle to the create cube surface", __FILE__, __LINE__);
			return false;
		}
	}

	return true;
}

void CubeMap::display(int offsetX, int offsetY, float scaleFactor, bool bIgnoreBlend, const math::Vector4 &color, bool enableFilter)
{
	coreGetLog()->WriteWarning("Could not display cube texture. Not implemented. ", __FILE__, __LINE__);
}

void CubeMap::beginScene(const math::Vector3 &position, eCubeFace face)
{
	// Save transformation matrices of the device
    pd3dDevice->GetTransform(D3DTS_VIEW,       &matViewSave);
    pd3dDevice->GetTransform(D3DTS_PROJECTION, &matProjSave);

    // Store the current back buffer and z-buffer
    pd3dDevice->GetRenderTarget(0, &pBackBuffer);
    pd3dDevice->GetDepthStencilSurface(&pZBuffer);

	// Use 90-degree field of view in the projection
    D3DMATRIX matProj;
    D3DXMatrixPerspectiveFovLH((D3DXMATRIX*)&matProj, D3DX_PI/2, 1.0f, 0.5f, 1000.0f);
	pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);

	// Standard view that will be overridden below
	static D3DXVECTOR3 vLookatPt, vUpVec;


	switch(face)
    {
        case POSITIVE_X:
			vLookatPt = D3DXVECTOR3(1.0f, 0.0f, 0.0f);
            vUpVec    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
            break;
        case NEGATIVE_X:
            vLookatPt = D3DXVECTOR3(-1.0f, 0.0f, 0.0f);
            vUpVec    = D3DXVECTOR3( 0.0f, 1.0f, 0.0f);
            break;
        case POSITIVE_Y:
            vLookatPt = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
            vUpVec    = D3DXVECTOR3(0.0f, 0.0f,-1.0f);
            break;
        case NEGATIVE_Y:
            vLookatPt = D3DXVECTOR3(0.0f,-1.0f, 0.0f);
            vUpVec    = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
            break;
        case POSITIVE_Z:
            vLookatPt = D3DXVECTOR3( 0.0f, 0.0f, 1.0f);
            vUpVec    = D3DXVECTOR3( 0.0f, 1.0f, 0.0f);
            break;
        case NEGATIVE_Z:
            vLookatPt = D3DXVECTOR3(0.0f, 0.0f,-1.0f);
            vUpVec    = D3DXVECTOR3(0.0f, 1.0f, 0.0f);
            break;
    }

     D3DMATRIX matView;
     D3DXMatrixLookAtLH((D3DXMATRIX*)&matView, (D3DXVECTOR3*) &position.x, &vLookatPt, &vUpVec);
     pd3dDevice->SetTransform(D3DTS_VIEW, &matView);

	 // Get pointer to surface in order to render to it
	static LPDIRECT3DSURFACE9 pFace;
	pCubeTexture->GetCubeMapSurface((D3DCUBEMAP_FACES)face, 0, &pFace);
	pd3dDevice->SetRenderTarget(0, pFace);
	pd3dDevice->SetDepthStencilSurface( pZBuffer );
	RELEASE(pFace);

	pd3dDevice->BeginScene();
}

bool CubeMap::endScene()
{
	pd3dDevice->EndScene();
	pd3dDevice->SetRenderTarget(0, pBackBuffer);
	pd3dDevice->SetDepthStencilSurface( pZBuffer );
    
	RELEASE(pBackBuffer);
    RELEASE(pZBuffer);

    // Restore the original transformation matrices
    pd3dDevice->SetTransform(D3DTS_VIEW,       &matViewSave);
    pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProjSave);
	return true;
}

} // namespace
